#include "http_connection.h"


namespace johnsonli
{
namespace http
{
    static johnsonli::Logger::ptr g_logger = LOG_NAME("system");

    std::string HttpResult::toString() const {
        std::stringstream ss;
        ss << "[HttpResult result=" << result
        << " error=" << error
        << " response=" << (response ? response->toString() : "nullptr")
        << "]";
        return ss.str();
    }
        
    HttpConnection::HttpConnection(Socket::ptr sock, bool owner)
        :SocketStream(sock, owner) 
    {}

    HttpConnection::~HttpConnection() 
    {
        LOG_DEBUG(g_logger) << "HttpConnection::~HttpConnection";
    }

    HttpResult::ptr HttpConnection::DoGet(const std::string& url
                            , uint64_t timeout_ms
                            , bool is_close
                            , const std::map<std::string, std::string>& headers
                            , const std::string& body)
    {
        Uri::ptr uri = Uri::Create(url);
        //LOG_INFO(g_logger) << "uri: " << uri->toString();
        if(uri == nullptr)
        {
            return std::make_shared<HttpResult>((int)HttpResult::Error::INVALID_URL
                    , nullptr, "invalid url: " + url);
        }

        return DoGet(uri, timeout_ms, is_close, headers, body);
    }

    HttpResult::ptr HttpConnection::DoGet(Uri::ptr uri
                            , uint64_t timeout_ms
                            , bool is_close
                            , const std::map<std::string, std::string>& headers
                            , const std::string& body)
    {
        return DoRequest(HttpMethod::GET, uri, timeout_ms, is_close, headers, body);
    }

    HttpResult::ptr HttpConnection::DoPost(const std::string& url
                            , uint64_t timeout_ms
                            , bool is_close
                            , const std::map<std::string, std::string>& headers
                            , const std::string& body)
    {
        Uri::ptr uri = Uri::Create(url);
        if(uri == nullptr)
        {
            return std::make_shared<HttpResult>((int)HttpResult::Error::INVALID_URL
                    , nullptr, "invalid url: " + url);
        }

        return DoPost(uri, timeout_ms, is_close, headers, body);
    }


    HttpResult::ptr HttpConnection::DoPost(Uri::ptr uri
                            , uint64_t timeout_ms
                            , bool is_close
                            , const std::map<std::string, std::string>& headers
                            , const std::string& body)
    {
        return DoRequest(HttpMethod::POST, uri, timeout_ms, is_close, headers, body);
    }


    HttpResult::ptr HttpConnection::DoRequest(HttpMethod method
                            , const std::string& url
                            , uint64_t timeout_ms
                            , bool is_close
                            , const std::map<std::string, std::string>& headers
                            , const std::string& body)
    {
        Uri::ptr uri = Uri::Create(url);
        if(!uri) {
            return std::make_shared<HttpResult>((int)HttpResult::Error::INVALID_URL
                    , nullptr, "invalid url: " + url);
        }
        return DoRequest(method, uri, timeout_ms, is_close, headers, body);
    }


    HttpResult::ptr HttpConnection::DoRequest(HttpMethod method
                            , Uri::ptr uri
                            , uint64_t timeout_ms
                            , bool is_close
                            , const std::map<std::string, std::string>& headers
                            , const std::string& body)
    {
        HttpRequest::ptr req = std::make_shared<HttpRequest>();

        req->setPath(uri->getPath());
        req->setQuery(uri->getQuery());
        req->setFragment(uri->getFragment());
        req->setMethod(method);
        req->setClose(is_close);
        

        bool has_host = false;
        bool has_connection = false;
        for(auto& i : headers)
        {
            //有connection字段
            if(!has_connection &&strcasecmp(i.first.c_str(), "connection") == 0)
            {
                //长连接，就设置不自动关闭
                if(strcasecmp(i.second.c_str(), "keep-alive") == 0)
                {
                    req->setClose(false);
                }
                has_connection = true;
                continue;
            }

            //有无host
            if(!has_host && strcasecmp(i.first.c_str(), "host") == 0) 
            {
                has_host = !i.second.empty();
            }

            req->setHeader(i.first, i.second);
        }

        //没有host，就要自己加
        if(!has_host) 
        {
            req->setHeader("Host", uri->getHost());
        }

        if(!has_connection && !is_close)
        {
            req->setHeader("connection", "keep-alive");
        }

        req->setBody(body);

        //LOG_INFO(g_logger) << "req: " << *req;

        return DoRequest(req, uri, timeout_ms);

    }

    HttpResult::ptr HttpConnection::DoRequest(HttpRequest::ptr req
                            , Uri::ptr uri
                            , uint64_t timeout_ms
                            , bool is_close)
    {
        //如果不自动关闭，设置长连接
        req->setClose(is_close);
        if(!is_close)
        {
            std::string val = "";
            std::string key = "connection";
            //不存在
            if(!req->checkGetParamAs(key, val))
            {
                req->setHeader("connection", "keep-alive");
            }
        }
        bool is_ssl = uri->getScheme() == "https";
        
        Address::ptr addr = uri->createAddress();
        if(!addr) 
        {
            return std::make_shared<HttpResult>((int)HttpResult::Error::INVALID_HOST
                    , nullptr, "invalid host: " + uri->getHost());
        }

        //创建socket
        Socket::ptr sock = Socket::CreateTCP(addr);
        if(!sock) 
        {
            return std::make_shared<HttpResult>((int)HttpResult::Error::CREATE_SOCKET_ERROR
                    , nullptr, "create socket fail: " + addr->toString()
                            + " errno=" + std::to_string(errno)
                            + " errstr=" + std::string(strerror(errno)));
        }

        //连接
        if(!sock->connect(addr)) 
        {
            return std::make_shared<HttpResult>((int)HttpResult::Error::CONNECT_FAIL
                    , nullptr, "connect fail: " + addr->toString());
        }

        //设置超时时常
        sock->setRecvTimeout(timeout_ms);

        HttpConnection::ptr conn = std::make_shared<HttpConnection>(sock);
        
        //发送请求
        int rt = conn->sendRequest(req);
        
        LOG_INFO(g_logger) << "sendRequest";

        if(rt == 0) 
        {
            return std::make_shared<HttpResult>((int)HttpResult::Error::SEND_CLOSE_BY_PEER
                    , nullptr, "send request closed by peer: " + addr->toString());
        }
        if(rt < 0) 
        {
            return std::make_shared<HttpResult>((int)HttpResult::Error::SEND_SOCKET_ERROR
                        , nullptr, "send request socket error errno=" + std::to_string(errno)
                        + " errstr=" + std::string(strerror(errno)));
        }

        //接收请求
        auto rsp = conn->recvResponse();
        LOG_INFO(g_logger) << "sendRequest";
        if(!rsp) 
        {
            return std::make_shared<HttpResult>((int)HttpResult::Error::TIMEOUT
                        , nullptr, "recv response timeout: " + addr->toString()
                        + " timeout_ms:" + std::to_string(timeout_ms));
        }

        return std::make_shared<HttpResult>((int)HttpResult::Error::OK, rsp, "ok");
    }


    HttpResponse::ptr HttpConnection::recvResponse() 
    {
        HttpResponseParser::ptr parser(new HttpResponseParser);
        uint64_t buff_size = HttpRequestParser::GetHttpRequestBufferSize();
        //uint64_t buff_size = 100;
        std::shared_ptr<char> buffer(
                new char[buff_size + 1], [](char* ptr){
                    delete[] ptr;
                });
        char* data = buffer.get();
        int offset = 0;
        do {
            int len = read(data + offset, buff_size - offset);
            if(len <= 0) {
                close();
                return nullptr;
            }
            len += offset;
            data[len] = '\0';
            size_t nparse = parser->execute(data, len, false);
            if(parser->hasError()) {
                close();
                return nullptr;
            }
            offset = len - nparse;
            if(offset == (int)buff_size) {
                close();
                return nullptr;
            }
            if(parser->isFinished()) {
                break;
            }
        } while(true);
        auto& client_parser = parser->getParser();
        std::string body;
        if(client_parser.chunked) {
            int len = offset;
            do {
                bool begin = true;
                do {
                    if(!begin || len == 0) {
                        int rt = read(data + len, buff_size - len);
                        if(rt <= 0) {
                            close();
                            return nullptr;
                        }
                        len += rt;
                    }
                    data[len] = '\0';
                    size_t nparse = parser->execute(data, len, true);
                    if(parser->hasError()) {
                        close();
                        return nullptr;
                    }
                    len -= nparse;
                    if(len == (int)buff_size) {
                        close();
                        return nullptr;
                    }
                    begin = false;
                } while(!parser->isFinished());
                //len -= 2;
                
                LOG_DEBUG(g_logger) << "content_len=" << client_parser.content_len;
                if(client_parser.content_len + 2 <= len) {
                    body.append(data, client_parser.content_len);
                    memmove(data, data + client_parser.content_len + 2
                            , len - client_parser.content_len - 2);
                    len -= client_parser.content_len + 2;
                } else {
                    body.append(data, len);
                    int left = client_parser.content_len - len + 2;
                    while(left > 0) {
                        int rt = read(data, left > (int)buff_size ? (int)buff_size : left);
                        if(rt <= 0) {
                            close();
                            return nullptr;
                        }
                        body.append(data, rt);
                        left -= rt;
                    }
                    body.resize(body.size() - 2);
                    len = 0;
                }
            } while(!client_parser.chunks_done);
        } else {
            int64_t length = parser->getContentLength();
            if(length > 0) {
                body.resize(length);

                int len = 0;
                if(length >= offset) {
                    memcpy(&body[0], data, offset);
                    len = offset;
                } else {
                    memcpy(&body[0], data, length);
                    len = length;
                }
                length -= offset;
                if(length > 0) {
                    if(readFixSize(&body[len], length) <= 0) {
                        close();
                        return nullptr;
                    }
                }
            }
        }
        if(!body.empty()) {
            auto content_encoding = parser->getData()->getHeader("content-encoding");
            LOG_DEBUG(g_logger) << "content_encoding: " << content_encoding
                << " size=" << body.size();
            if(strcasecmp(content_encoding.c_str(), "gzip") == 0) {
                auto zs = ZlibStream::CreateGzip(false);
                zs->write(body.c_str(), body.size());
                zs->flush();
                zs->getResult().swap(body);
            } else if(strcasecmp(content_encoding.c_str(), "deflate") == 0) {
                auto zs = ZlibStream::CreateDeflate(false);
                zs->write(body.c_str(), body.size());
                zs->flush();
                zs->getResult().swap(body);
            }
            parser->getData()->setBody(body);
        }
        return parser->getData();
    }

    int HttpConnection::sendRequest(HttpRequest::ptr req) 
    {
        std::stringstream ss;
        ss << *req;
        std::string data = ss.str();
        //std::cout << ss.str() << std::endl;
        return writeFixSize(data.c_str(), data.size());
    }


    HttpConnectionPool::ptr HttpConnectionPool::Create(const std::string& uri
                                                   ,const std::string& vhost
                                                   ,uint32_t max_size
                                                   ,uint32_t max_alive_time
                                                   ,uint32_t max_request) 
    {
        Uri::ptr turi = Uri::Create(uri);
        if(!turi) 
        {
            LOG_ERROR(g_logger) << "invalid uri=" << uri;
        }

         return std::make_shared<HttpConnectionPool>(
                turi->getHost()
                , vhost, turi->getPort(), turi->getScheme() == "https"
                , max_size, max_alive_time, max_request);
    }

    HttpConnectionPool::HttpConnectionPool(const std::string& host
                                        ,const std::string& vhost
                                        ,uint32_t port
                                        ,bool is_https
                                        ,uint32_t max_size
                                        ,uint32_t max_alive_time
                                        ,uint32_t max_request)
        :m_host(host)
        ,m_vhost(vhost)
        ,m_port(port ? port : (is_https ? 443 : 80))
        ,m_maxSize(max_size)
        ,m_maxAliveTime(max_alive_time)
        ,m_maxRequest(max_request)
        ,m_isHttps(is_https) 
    {}

    HttpConnection::ptr HttpConnectionPool::getConnection() 
    {
        uint64_t now_ms = johnsonli::GetCurrentMS();
        std::vector<HttpConnection*> invalid_conns;
        HttpConnection* ptr = nullptr;

        MutexType::Lock lock(m_mutex);
        while(!m_conns.empty())
        {
            auto conn = *m_conns.begin();
            m_conns.pop_front();

            //当前连接已经断开连接，无效
            if(!conn->isConnected())
            {
                invalid_conns.push_back(conn);
                continue;
            }

            //到期，无效
            if((conn->getCreateTime() + m_maxAliveTime) < now_ms)
            {
                invalid_conns.push_back(conn);
                continue;
            }
            ptr = conn;
            break;
        }

        lock.unlock();

        //释放无效的连接
        for(auto i : invalid_conns)
        {
            delete i;
        }
        m_total -= invalid_conns.size();

        //没有可用的连接，创建
        if(!ptr)
        {
            IPAddress::ptr addr = Address::LookupAnyIPAddress(m_host);
            if(!addr) 
            {
                LOG_ERROR(g_logger) << "get addr fail: " << m_host;
                return nullptr;
            }
            addr->setPort(m_port);

            //创建socket
            Socket::ptr sock = Socket::CreateTCP(addr);
            if(!sock) 
            {
                LOG_ERROR(g_logger) << "create sock fail: " << *addr;
                return nullptr;
            }

            //连接
            //ptr->setRequestCount(ptr->getRequestCount()+1);
            if(!sock->connect(addr)) 
            {
                LOG_ERROR(g_logger) << "sock connect fail: " << *addr;
                return nullptr;
            }
            
            ptr = new HttpConnection(sock);
            ptr->setCreateTime(johnsonli::GetCurrentMS());
            ++m_total;
        }

        return HttpConnection::ptr(ptr, std::bind(&HttpConnectionPool::ReleasePtr
                            , std::placeholders::_1, this));
    }

    void HttpConnectionPool::ReleasePtr(HttpConnection* ptr, HttpConnectionPool* pool) 
    {
        //LOG_INFO(g_logger) << "ReleasePtr";
        //连接数+1
        //ptr->setRequestCount(ptr->getRequestCount()+1);

        //断开连接，存活到期，请求数量太多，销毁此连接
        if(!ptr->isConnected()
            || (ptr->getCreateTime() + pool->m_maxAliveTime) < johnsonli::GetCurrentMS()
            || (ptr->getRequestCount() >= pool->m_maxRequest))
        {
            //LOG_INFO(g_logger) << "ptr->getRequestCount(): " << ptr->getRequestCount();
            delete ptr;
            --pool->m_total;
            return;
        }

        MutexType::Lock lock(pool->m_mutex);
        //如果当前连接还可用，就加入连接池
        pool->m_conns.push_back(ptr);
    }

    HttpResult::ptr HttpConnectionPool::doGet(const std::string& url
                                            , uint64_t timeout_ms
                                            , const std::map<std::string, std::string>& headers
                                            , const std::string& body) 
    {
        return doRequest(HttpMethod::GET, url, timeout_ms, headers, body);
    }

    HttpResult::ptr HttpConnectionPool::doGet(Uri::ptr uri
                                    , uint64_t timeout_ms
                                    , const std::map<std::string, std::string>& headers
                                    , const std::string& body) 
    {
        std::stringstream ss;
        ss << uri->getPath()
        << (uri->getQuery().empty() ? "" : "?")
        << uri->getQuery()
        << (uri->getFragment().empty() ? "" : "#")
        << uri->getFragment();
        return doGet(ss.str(), timeout_ms, headers, body);
    }

    HttpResult::ptr HttpConnectionPool::doPost(const std::string& url
                                    , uint64_t timeout_ms
                                    , const std::map<std::string, std::string>& headers
                                    , const std::string& body) 
    {
        return doRequest(HttpMethod::POST, url, timeout_ms, headers, body);
    }

    HttpResult::ptr HttpConnectionPool::doPost(Uri::ptr uri
                                    , uint64_t timeout_ms
                                    , const std::map<std::string, std::string>& headers
                                    , const std::string& body) 
    {
        std::stringstream ss;
        ss << uri->getPath()
        << (uri->getQuery().empty() ? "" : "?")
        << uri->getQuery()
        << (uri->getFragment().empty() ? "" : "#")
        << uri->getFragment();
        return doPost(ss.str(), timeout_ms, headers, body);
    }

    HttpResult::ptr HttpConnectionPool::doRequest(HttpMethod method
                                        , const std::string& url
                                        , uint64_t timeout_ms
                                        , const std::map<std::string, std::string>& headers
                                        , const std::string& body) 
    {
        HttpRequest::ptr req = std::make_shared<HttpRequest>();
        req->setPath(url);
        req->setMethod(method);
        req->setClose(false);   //实现长连接
        bool has_host = false;
        bool has_connection = false;
        for(auto& i : headers) {
            if(!has_connection && strcasecmp(i.first.c_str(), "connection") == 0) {
                if(strcasecmp(i.second.c_str(), "keep-alive") == 0) {
                    req->setClose(false);
                }  
                has_connection = true;
                continue;
            }

            if(!has_host && strcasecmp(i.first.c_str(), "host") == 0) {
                has_host = !i.second.empty();
            }

            req->setHeader(i.first, i.second);
        }

        //设置长连接
        if(!has_connection)
        {
            req->setHeader("connection", "keep-alive");
        }
        

        if(!has_host) {
            if(m_vhost.empty()) {
                req->setHeader("Host", m_host);
            } else {
                req->setHeader("Host", m_vhost);
            }
        }
        req->setBody(body);

        //LOG_INFO(g_logger) << "req: " << *req;
        return doRequest(req, timeout_ms);
    }

    HttpResult::ptr HttpConnectionPool::doRequest(HttpMethod method
                                        , Uri::ptr uri
                                        , uint64_t timeout_ms
                                        , const std::map<std::string, std::string>& headers
                                        , const std::string& body) 
    {
        std::stringstream ss;
        ss << uri->getPath()
        << (uri->getQuery().empty() ? "" : "?")
        << uri->getQuery()
        << (uri->getFragment().empty() ? "" : "#")
        << uri->getFragment();
        return doRequest(method, ss.str(), timeout_ms, headers, body);
    }

    HttpResult::ptr HttpConnectionPool::doRequest(HttpRequest::ptr req
                                            , uint64_t timeout_ms) 
    {
        
        auto conn = getConnection();
        conn->setRequestCount(conn->getRequestCount()+1);   //每做一次请求，多一个请求
        LOG_INFO(g_logger) << "getConnection";
        if(!conn) 
        {
            return std::make_shared<HttpResult>((int)HttpResult::Error::POOL_GET_CONNECTION
                    , nullptr, "pool host:" + m_host + " port:" + std::to_string(m_port));
        }
        auto sock = conn->getSocket();
        if(!sock) 
        {
            return std::make_shared<HttpResult>((int)HttpResult::Error::POOL_INVALID_CONNECTION
                    , nullptr, "pool host:" + m_host + " port:" + std::to_string(m_port));
        }
        sock->setRecvTimeout(timeout_ms);
        int rt = conn->sendRequest(req);
        if(rt == 0) 
        {
            return std::make_shared<HttpResult>((int)HttpResult::Error::SEND_CLOSE_BY_PEER
                    , nullptr, "send request closed by peer: " + sock->getRemoteAddress()->toString());
        }
        if(rt < 0) 
        {
            return std::make_shared<HttpResult>((int)HttpResult::Error::SEND_SOCKET_ERROR
                        , nullptr, "send request socket error errno=" + std::to_string(errno)
                        + " errstr=" + std::string(strerror(errno)));
        }
        auto rsp = conn->recvResponse();
        if(!rsp) 
        {
            return std::make_shared<HttpResult>((int)HttpResult::Error::TIMEOUT
                        , nullptr, "recv response timeout: " + sock->getRemoteAddress()->toString()
                        + " timeout_ms:" + std::to_string(timeout_ms));
        }
        return std::make_shared<HttpResult>((int)HttpResult::Error::OK, rsp, "ok");

        // 退出时，conn会主动释放，此时会执行ReleasePtr
    }

}
}